﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Visualizator;
using Descriptions_DomainModel.Model;
using CommonClasses;
using Eco.Services;
using CommonClasses.Services;
using Eco.Handles;
using VMLua;
using Eco.ObjectRepresentation;
using VisualDM.Model;
using CommonClasses.Logger;
using Axiom;
using Axiom.Core;
using Axiom.Math;

namespace ModellingSupervisor
{
    /// <summary>
    /// Модуль, моделирующий работу виритуального оборудования посредством использования скриптов
    /// </summary>
    public class ModellingSupervisor : IConnection
    {
        /// <summary>
        /// Логгер
        /// </summary>
        private ILogger logger = null;

        /// <summary>
        /// Доступ к сервисам ECO
        /// </summary>
        private IEcoServiceProvider ecoServiceProvider;

        /// <summary>
        /// Виртуальнам машина Lua
        /// </summary>
        private IVirtualMachineService luaVM
        {
            get
            {
                return ecoServiceProvider.GetEcoService<IVirtualMachineService>();
            }
        }

        /// <summary>
        /// Вирт. оборудование
        /// </summary>
        private VisualDM.Model.VisualDomainModel VDM;

        /// <summary>
        /// Описание оборудования
        /// </summary>
        private DomainModel DM;

        /// <summary>
        /// Таблица пинов
        /// </summary>
        /// <remarks>
        /// Нужна для SetCheckPoint
        /// </remarks>
        private VirtualPinTable pinTable;

        /// <summary>
        /// Установленные точки возврата
        /// </summary>
        private Stack<CheckPoint> CheckPoints = new Stack<CheckPoint>();

        private Dictionary<string, SceneObjectsLink> ConnectsSceneObjects = new Dictionary<string, SceneObjectsLink>();
        private Dictionary<string, BolvanLinks> ConnectsBolvanks = new Dictionary<string, BolvanLinks>();

        /// <summary>
        /// Список всех методов
        /// </summary>
        private IEnumerable<Method> allMethods;

        /// <summary>
        /// Номера пинов состояния всех узлов
        /// </summary>
        private IEnumerable<int> statePins;

        /// <summary>
        /// Включен ли режим моделирования
        /// </summary>
        private bool isModelling = true;

        private int countCallsIntersect = 0;

        /// <summary>
        /// Окончание моделирования
        /// </summary>
        public void ResetModelling()
        {
            ConnectsSceneObjects.Clear();
            CheckPoints.Clear();
            ConnectsBolvanks.Clear();
        }

        private void RegisterLuaObject(SceneObject o)
        {
            if (o != null)
            {
                luaVM.RegisterObject(o.Name, o);
                foreach (SceneObject o1 in o.ChildSceneObjects)
                    RegisterLuaObject(o1);
            }
        }

        public ModellingSupervisor(DomainModel dm, VisualDomainModel vdm, VirtualPinTable pintable, ILoggingManagement loggingManagement)
        {
            logger = loggingManagement.GetLogger("ModellingModuleLogger");

            VDM = vdm;
            DM = dm;
            this.pinTable = pintable;

            //получили EcoSpace из объектов
            ecoServiceProvider = dm.AsIObject().ServiceProvider;
            var ocl = ecoServiceProvider.GetEcoService<IOclService>();

            //получаем все методы
            allMethods = ocl.Evaluate("Method.allInstances").GetAsIList<Method>();

            var allnodes = ocl.Evaluate("Node.allInstances")
                    .GetAsIList<Descriptions_DomainModel.Model.Node>();
            statePins = from n in allnodes select n.StatePinNumber;

            allAnimations = GetAllSubAnimations(VDM.Scene);

            RegisterLuaObject(VDM.Scene);

            luaVM.RegisterObject("ModellingSupervisor", this);
        }

        /// <summary>
        /// Получить список всех анимаций объекта и всех его подобъектов
        /// </summary>
        /// <param name="sceneObject">Объект</param>
        /// <returns></returns>
        private List<Animation> GetAllSubAnimations(SceneObject sceneObject)
        {
            List<Animation> anims = new List<Animation>();

            anims.AddRange(sceneObject.Animations);
            foreach (SceneObject so in sceneObject.ChildSceneObjects)
                anims.AddRange(GetAllSubAnimations(so));
            return anims;
        }

        /// <summary>
        /// Найти анимацию по идентификатору метода
        /// </summary>
        /// <param name="o">объект сцены</param>
        /// <param name="methodId">идентификатор метода</param>
        /// <returns></returns>
        private Animation FindAnimationScript(SceneObject o, string methodId)
        {
            return allAnimations.Find(an => an.MethodID == methodId);
        }

        private SceneObject GetSceneObjectOfAnimationScript(SceneObject o, string oldid, string name)
        {
            SceneObject an = null;
            if (o != null)
            {
                //Ищем у себя
                foreach (Animation a in o.Animations)
                    if ((a.MethodID == oldid) && (a.Name == name))
                    {
                        return o;
                    }
                //если не нашли то ищем у детей ;)
                if (an == null)
                    foreach (SceneObject o1 in o.ChildSceneObjects)
                    {
                        an = GetSceneObjectOfAnimationScript(o1, oldid, name);
                        if (an != null)
                            return an;
                    }
            }
            return null;
        }

        #region IConnection members

        public event PinStateChangedHandler OnPinStateChanged;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a">объект, который вызвал проверку пересечения</param>
        /// <param name="o">кого захотели</param>
        /// <returns></returns>
        private SceneObject IntersectChildSceneObjects(SceneObject a, SceneObject o)
        {
            if (!(((a.TypeModelling & 0x0001) != 0) && ((o.TypeModelling & 0x0001) != 0))/* && ((o.TypeModelling & 0x0008) != 0)*/)
                return null;
            if (VisualizationService.AABBIntersectAABB(((VisibleSceneObject)a).SceneNode.WorldAABB, ((VisibleSceneObject)o).SceneNode.WorldAABB))
            {
                //проверимся
                if (((VisibleSceneObject)a).TestIntersect((VisibleSceneObject)o))
                    return o;
                //иначе проверим не имел ли связи со мной кто-то из потомков
                foreach (SceneObject s in o.ChildSceneObjects)
                {
                    SceneObject c = IntersectChildSceneObjects(a, s);
                    if (c != null)
                        return c;
                }
                //сюда зашли если никто из потомков не имел связи с объектом сталкивания
                //поэтому проверим связь со мной

                foreach (SceneObject s in a.ChildSceneObjects)
                {
                    SceneObject c = IntersectChildSceneObjects(s, o);
                    if (c != null)
                        return c;
                }
            }
            return null;
        }

        /// <summary>
        /// Проверить пересечение объекта 'a' с объектом 'о' и его потомками
        /// </summary>
        /// <param name="a"></param>
        /// <param name="o"></param>
        /// <returns></returns>
        private SceneObject IntersectObjectsWithoutFirstObjectChilds(SceneObject a, SceneObject o)
        {
            if (!(((a.TypeModelling & 0x0001) != 0) && ((o.TypeModelling & 0x0001) != 0)))
                return null;
            if (VisualizationService.AABBIntersectAABB(((VisibleSceneObject)a).SceneNode.WorldAABB, ((VisibleSceneObject)o).SceneNode.WorldAABB))
            {
                //проверимся
                if (((VisibleSceneObject)a).TestIntersect((VisibleSceneObject)o))
                    return o;
                //иначе проверим не имел ли связи со мной кто-то из потомков
                foreach (SceneObject s in o.ChildSceneObjects)
                {
                    //Console.WriteLine("OnPhisicModelling begin time = " + DateTime.Now.ToString("hh:mm:ss.fff"));
                    SceneObject c = IntersectObjectsWithoutFirstObjectChilds(a, s);
                    //Console.WriteLine("OnPhisicModelling end time = " + DateTime.Now.ToString("hh:mm:ss.fff"));
                    if (c != null)
                        return c;
                }
            }
            return null;
        }

        private bool PhisModellingRec(SceneObject o, SceneObject ob)
        {
            bool f = false; ;
            if (((ob.TypeModelling & 0x0001) != 0) && ((ob.TypeModelling & 0x0002) != 0))//TypeModelling == 0x0001 && TypeModelling == 0x0002
            {
                if (VisualizationService.AABBIntersectAABB(ob.SceneNode.WorldAABB, o.SceneNode.WorldAABB))
                {
                    //Console.WriteLine("OnPhisicModelling begin time = " + DateTime.Now.ToString("hh:mm:ss.fff"));
                    bool p = ((VisibleSceneObject)o).TestIntersect((VisibleSceneObject)ob);
                    countCallsIntersect++;
                    //Console.WriteLine("OnPhisicModelling end time = " + DateTime.Now.ToString("hh:mm:ss.fff"));
                    if (p/*((VisibleSceneObject)o).TestIntersect((VisibleSceneObject)ob)*/)
                        f = true;
                    else
                    {
                        foreach (SceneObject s in ob.ChildSceneObjects)
                        {
                            if (PhisModellingRec(o, s))
                            {
                                return true;
                            }
                        }
                        foreach (SceneObject s in o.ChildSceneObjects)
                        {
                            if (PhisModellingRec(s, ob))
                            {
                                return true;
                            }
                        }
                    }
                }
            }
            return f;
        }

        public void OnPhisicModelling()
        {//здесь будет только имитировать силу тяжести. причем только те объекты, у которых первым предком является сцена

            
//            Console.WriteLine("OnPhisicModelling begin time = " + DateTime.Now.ToString("hh:mm:ss.fff"));

            countCallsIntersect=0;

            Dictionary<String, SceneObject> d = new Dictionary<string, SceneObject>();

            
            foreach (SceneObject ob in VDM.Scene.ChildSceneObjects)
            {
                if (((ob.TypeModelling & 0x0001) != 0) && (ob.TypeModelling & 0x0004) != 0) //на объект действует сила тяжести?
                {
//                    d[ob.Name] = ob;
                    SceneObject intO = null; //объект, с которым пересеклись
                    if ((ConnectsSceneObjects.Keys.FirstOrDefault(meth => (ConnectsSceneObjects[meth].Dependent == ob)) == null))//вдруг случайно обнаружили што пересеклись с объектом, который кто-то схватил
                    {
                        bool f = false;
                        foreach (SceneObject o in VDM.Scene.ChildSceneObjects)
                        {
                            if ((o != ob))
                            {
                                if (PhisModellingRec(ob, o))
                                {
                                    f = true;
                                    intO = o;
                                }
                            }
                        }
                        if (!f)
                        {
                            String s = ob.SceneNode.Position.x.ToString() + ";" + ob.SceneNode.Position.y.ToString() + ";" + ob.SceneNode.Position.z.ToString();
                            s = ob.SceneNode.Orientation.w.ToString() + ";" + ob.SceneNode.Orientation.x.ToString() + ";" + ob.SceneNode.Orientation.y.ToString() + ";" + ob.SceneNode.Orientation.z.ToString();
                            ob.Translate(0.0f, -0.1f, 0.0f);
                        }
                        else
                        {
                            //сюда зайдем в том случае, если произошло пересечение. мы не знаем в какой последовательности будуь в списк объекты, поэтому проверим объекты на каждое из своств
                            if ((((ob.TypeModelling & 0x0010) != 0) && ((intO.TypeModelling & 0x0020) != 0)) || ((intO.TypeModelling & 0x0010) != 0) && ((ob.TypeModelling & 0x0020) != 0)) //соберем деталь
                            {
                                if (ConnectsBolvanks.Keys.FirstOrDefault(meth => (ConnectsBolvanks[meth].MainBolv == ob)) == null)
                                {
                                    SceneObject localOb = ob;
                                    if ((localOb.TypeModelling & 0x0020) != 0)
                                    {
                                        var temp = localOb;
                                        localOb = intO;
                                        intO = temp;
                                    }

                                    //будем запихивать по имени присоединемого
                                    ConnectsBolvanks[intO.Name] = new BolvanLinks(localOb, intO); //запомнили связь

                                    SceneObject IntersParent = intO.Parent;

                                    #region меняем привязки и учитываем соединение (Link)

                                    intO.Parent.ChildSceneObjects.Remove(intO);
                                    localOb.AddChildSceneObject(intO);

                                    //тупо псевдоним
                                    Axiom.Core.SceneNode sn = intO.SceneNode;

                                    #region преобразовываем позицию

                                    //                                    Axiom.Math.Matrix4 m = ob.SceneNode.FullTransform;
                                    //Axiom.Math.Vector3 v = intO.SceneNode.DerivedPosition;
                                    //sn.Position = m.Inverse()  v;


                                    sn.Position = localOb.SceneNode.FullTransform.Inverse() * sn.DerivedPosition;

                                    Quaternion q = new Quaternion();
                                    q.FromRotationMatrix(localOb.SceneNode.FullTransform.Inverse().GetMatrix3());
                                    sn.Orientation = q * sn.DerivedOrientation;

                                    sn.Scale(IntersParent.SceneNode.DerivedScale);
                                    sn.Scale(Axiom.Math.Vector3.UnitScale / localOb.SceneNode.DerivedScale);

                                    #endregion

                                    //привязываем к узлу сцены - в самом конце, пока sn.Parent не поменялся
                                    localOb.SceneNode.AddChild(sn);
                                    //обновить часть сцены
                                    localOb.SceneNode.NeedUpdate();
                                    IntersParent.SceneNode.NeedUpdate();
                                    #endregion
                                }
                            }

                        }
                    }
                }
            }

//            Console.WriteLine("OnPhisicModelling begin time = " + DateTime.Now.ToString("hh:mm:ss.fff"));
            //Console.WriteLine("OnPhisicModelling count calls = " + countCallsIntersect.ToString());
//            Console.WriteLine("OnPhisicModelling end time = " + DateTime.Now.ToString("hh:mm:ss.fff"));

              
        }

        /// <summary>
        /// По приходе пина на модуль моделирования
        /// </summary>
        /// <param name="virtualPinOut">виртуальныый пин</param>
        /// <param name="signal">сигнал</param>
        /// <remarks>
        /// Если есть четкая анимация по пину и значению, то пускаем ее
        /// Иначе если signal = 0, то это похоже на .end
        /// Будем считать, что это .end, если signal = 0 и есть метод и анимация для него для данного пина и signal = 1, и нас просят удалить эту анимацию
        /// </remarks>
        public void OnPin(int virtualPinOut, int signal)
        {
            Console.WriteLine("ModellingSupervisor.OnPin({0},{1}):", virtualPinOut, signal);

            // метод по действию и сигналу
            Method methodExact = allMethods.FirstOrDefault(
                m => (m.VirtualPinOut == virtualPinOut) && (m.Signal == signal));

            // анимация для действия
            Animation animationExact = methodExact != null ? allAnimations.Find(an => (an.MethodID == methodExact.OldID)) : null;

            if ((animationExact == null) || ((!animationExact.IsModelledAlways) && (!isModelling)))
            {
                if (signal == 0)
                {
                    // метод с данным пином, но уже единицей
                    Method meth = allMethods.FirstOrDefault(
                        m => (m.VirtualPinOut == virtualPinOut) && (m.Signal == 1));

                    Animation anim = meth != null ? allAnimations.Find(an => (an.MethodID == meth.OldID)) : null;

                    if ((anim == null) || ((!anim.IsModelledAlways) && (!isModelling))) // значит вообще не обрабатываем этот пин
                    {
                        Console.WriteLine("ModellingSupervisor: no animationByVpin for vpin = {0}, signal = {1}", virtualPinOut, signal);
                        return;
                    }
                    else
                    {
                        #region Проверяем привязку(но тут не действует. перенесенав в другое место)
                        //switch (anim.NeedConnect)
                        //{
                        //    case 3:
                        //    case 4:
                        //        {
                        //            var cc = ConnectsSceneObjects.Keys.FirstOrDefault(m => (ConnectsSceneObjects[m].Animation == anim));
                        //            if (cc != null)
                        //                if (ConnectsSceneObjects.ContainsKey(cc))
                        //                {
                        //                    SceneObject s = GetSceneObjectOfAnimationScript(VDM.Scene, anim.MethodID, anim.Name);
                        //                    //--Связываем объекты
                        //                    Axiom.Math.Vector3 v = ConnectsSceneObjects[s.Name].Dependent.SceneNode.DerivedPosition;
                        //                    Axiom.Math.Quaternion or = ConnectsSceneObjects[s.Name].Dependent.SceneNode.DerivedOrientation;
                        //                    Axiom.Core.SceneNode sn = ConnectsSceneObjects[s.Name].Dependent.SceneNode;
                        //                    Axiom.Math.Matrix4 m = VDM.Scene.SceneNode.FullTransform;
                        //                    sn.Position = m.Inverse() * v;
                        //                    sn.Orientation = AxiomUtility.GetOrientationWithTransform(sn, s);
                        //                    sn.Scale(Axiom.Math.Vector3.UnitScale * s.SceneNode.DerivedScale);
                        //                    s.ChildSceneObjects.Remove(ConnectsSceneObjects[s.Name].Dependent);
                        //                    VDM.Scene.AddChildSceneObject(ConnectsSceneObjects[s.Name].Dependent);
                        //                    VDM.Scene.SceneNode.AddChild(sn);
                        //                    ConnectsSceneObjects.Remove(s.Name);
                        //                    s.SceneNode.NeedUpdate();
                        //                    VDM.Scene.SceneNode.NeedUpdate();
                        //                }
                        //            break;
                        //        }
                        //}
                        #endregion

                        UnregisterAnimation(anim);
                    }
                }
            }
            else
            {
                // не имеется ли уже такой анимации
                if ((animationExact.ForbidMult) && (loadedAnimations.Contains(animationExact)))
                    return;

                #region Привязка(но тут не действует)
                //switch (animationExact.NeedConnect)
                //{
                //    case 1:
                //    case 3:
                //    case 4:
                //        {
                //            SceneObject s = GetSceneObjectOfAnimationScript(VDM.Scene,
                //                animationExact.MethodID,
                //                animationExact.Name);

                //            if (ConnectsSceneObjects.ContainsKey(s.Name))
                //            {
                //                ConnectsSceneObjects[s.Name].Animation = animationExact;
                //                break;
                //            }

                //            //*************************************
                //            //Найти объект, который привязываем
                //            //*************************************
                //            SceneObject LastParent = s;
                //            while (LastParent.Parent != VDM.Scene)
                //                LastParent = LastParent.Parent;

                //            //Ищем первого того, с кем столкнулись - его будем привязывать к s
                //            SceneObject InterSectObject = null;
                //            int i = 0;
                //            while (i < VDM.Scene.ChildSceneObjects.Count)
                //            {
                //                SceneObject o = VDM.Scene.ChildSceneObjects[i];
                //                if (o.TypeModelling == 3)
                //                    if ((o != LastParent) && (VisualizationService.AABBIntersectAABB(s.SceneNode.WorldAABB, o.SceneNode.WorldAABB))) //а себя мы не будем хватать
                //                    {
                //                        InterSectObject = IntersectChildSceneObjects(s.SceneNode.WorldAABB, o);
                //                        if ((InterSectObject != null))// && (InterSectObject.TypeModelling != 2))//можно не брать всякие столики
                //                        {
                //                            // if ((s.Animations.Contains(animation))&&(animation.NeedConnect==3))
                //                            break;
                //                        }
                //                    }
                //                InterSectObject = null;
                //                i++;
                //            }
                //            //---------------------------------

                //            if (InterSectObject != null)
                //            {
                //                //--Связываем объекты
                //                //удаляем старую связь
                //                var cc = ConnectsSceneObjects.Keys.FirstOrDefault(meth => (ConnectsSceneObjects[meth].DependentOriginal == InterSectObject));
                //                if (cc != null)
                //                    break;

                //                SceneObject Dependent = InterSectObject;

                //                //ДЕРЬМО!!! НАДО ВСПОМНИТЬ И ПРОКОММЕНТИРОВАТЬ!!
                //                //ищем первого потомка сцены и предка привязываемого объекта
                //                //ААА!!! Я ПОНЯЛ, НАФИГА ЭТО НАДО!!!
                //                //это надо затем, чтобы сохранялась цельность объектов
                //                while (InterSectObject.Parent != VDM.Scene)
                //                    InterSectObject = InterSectObject.Parent;

                //                Axiom.Math.Vector3 v = InterSectObject.SceneNode.DerivedPosition;
                //                //Axiom.Math.Quaternion or = InterSectObject.SceneNode.Orientation;

                //                //Связываем с новым объектом - перекидываем Parent
                //                SceneObject IntersParent = InterSectObject.Parent;
                //                //отвязываем от старого

                //                #region меняем привязки и учитываем соединение (Link)

                //                InterSectObject.Parent.ChildSceneObjects.Remove(InterSectObject);
                //                s.AddChildSceneObject(InterSectObject);
                //                ConnectsSceneObjects[s.Name] = new SceneObjectsLink(s, InterSectObject, Dependent, IntersParent, animationExact);

                //                #endregion

                //                //тупо псевдоним
                //                Axiom.Core.SceneNode sn = InterSectObject.SceneNode;

                //                #region преобразовываем позицию

                //                Axiom.Math.Matrix4 m = s.SceneNode.FullTransform;
                //                sn.Position = m.Inverse() * v;

                //                #endregion

                //                #region преобразовываем масштаб - чтобы размеры объекта не изменились

                //                sn.Scale(IntersParent.SceneNode.DerivedScale);
                //                sn.Scale(Axiom.Math.Vector3.UnitScale / s.SceneNode.DerivedScale);

                //                #endregion

                //                 #region Преобразование ориентации

                //                        //получаем глобальную ориентацию привязываемого
                //                        var dependentGlobalOrient = sn.GetGlobalOrientation();
                //                        //получаем глобальную ориенатцию нового предка
                //                        var newParentGlobalOrient = s.SceneNode.GetGlobalOrientation();
                //                        //находим новую относительную ориентацию привязываемого
                //                        var difference = newParentGlobalOrient + dependentGlobalOrient;
                //                        sn.Orientation = Quaternion.FromEulerAngles(difference.x,
                //                            2 * difference.y,
                //                            difference.z - (difference.x + difference.y));

                //                #endregion
                //                sn.Orientation = AxiomUtility.GetOrientationWithTransform(sn, s);

                //                //привязываем к узлу сцены - в самом конце, пока sn.Parent не поменялся
                //                s.SceneNode.AddChild(sn);
                //                //обновить часть сцены
                //                s.SceneNode.NeedUpdate();
                //                IntersParent.SceneNode.NeedUpdate();

                //            }

                //            break;
                //        }
                //    case 2:
                //        {
                //            SceneObject s = GetSceneObjectOfAnimationScript(VDM.Scene, animationExact.MethodID, animationExact.Name);
                //            if (ConnectsSceneObjects.ContainsKey(s.Name))
                //            {
                //                //--Связываем объекты
                //                Axiom.Math.Vector3 v = ConnectsSceneObjects[s.Name].Dependent.SceneNode.DerivedPosition;
                //                Axiom.Math.Quaternion or = ConnectsSceneObjects[s.Name].Dependent.SceneNode.DerivedOrientation;
                //                Axiom.Core.SceneNode sn = ConnectsSceneObjects[s.Name].Dependent.SceneNode;
                //                Axiom.Math.Matrix4 m = VDM.Scene.SceneNode.FullTransform;
                //                sn.Position = m.Inverse() * v;
                //                #region Преобразование ориентации

                //                /*                                        //получаем глобальную ориентацию привязываемого
                //                        var dependentGlobalOrient = sn.GetGlobalOrientation();
                //                        //получаем глобальную ориенатцию нового предка
                //                        var newParentGlobalOrient = s.SceneNode.GetGlobalOrientation();
                //                        //находим новую относительную ориентацию привязываемого
                //                        var difference = newParentGlobalOrient + dependentGlobalOrient;
                //                        sn.Orientation = Quaternion.FromEulerAngles(difference.x,
                //                            2 * difference.y,
                //                            difference.z - (difference.x + difference.y));*/
                //                sn.Orientation = AxiomUtility.GetOrientationWithTransform(sn, VDM.Scene);

                //                #endregion
                //                sn.Scale(s.SceneNode.DerivedScale);
                //                VDM.Scene.AddChildSceneObject(ConnectsSceneObjects[s.Name].Dependent);
                //                VDM.Scene.SceneNode.AddChild(sn);
                //                s.ChildSceneObjects.Remove(ConnectsSceneObjects[s.Name].Dependent);
                //                ConnectsSceneObjects.Remove(s.Name);
                //                s.SceneNode.NeedUpdate();
                //                VDM.Scene.SceneNode.NeedUpdate();
                //            }
                //            break;
                //        }
                //}
                #endregion

                RegisterAnimation(animationExact);
            }
        }

        public bool MatchRange(int VirtualPin)
        {
            throw new NotImplementedException();
            /*
            var workmethods = (isModelling) ? allMethods : handledMethods;

            var method = workmethods.FirstOrDefault(
                meth => meth.VirtualPinOut == VirtualPin);

            if (method == null)
                return false;
            if (FindAnimationScript(VDM.Scene, method.OldID) != null)
                return true;
            else return false;
             */
        }

        /// <summary>
        /// Начать моделирование
        /// </summary>
        /// <remarks>
        /// Запустить постоянные анимации
        /// </remarks>
        public void Begin()
        {
            // запустить постоянные анимации с permanent = true  и которые относятся к методам, соответствующим текущему isModelling
            foreach (Animation anim in GetAllSubAnimations(VDM.Scene))
                if ((anim.Permanent) && ((isModelling) || (anim.IsModelledAlways)))
                    RegisterAnimation(anim);
        }

        /// <summary>
        /// Завершить моделирование
        /// </summary>
        public void End()
        {
            loadedAnimations.Clear();
        }

        public void FlushPins()
        {
            ResetModelling();
        }

        public void Dispose()
        {
            return;
        }

        #endregion

        #region Checkpoints

        public void SetCheckPoint()
        {
            CheckPoint p = new CheckPoint();
            //Формируем таблицу значений виртуальных пинов
            foreach (var pin in statePins)
            {
                p.vpinsIn[pin] = pinTable.GetPinInStatus(pin);
            }
            p.ConnectsSceneObjects = new Dictionary<string, SceneObjectsLink>(ConnectsSceneObjects);
            loadedAnimations.ForEach(anim =>
                {
                    if (anim.Name != "SetCheckPoint")
                        p.animations.Add(anim);
                });
            CheckPoints.Push(p);

            VDM.Scene.SetCheckPoint();
        }

        public void RollBack()
        {
            CheckPoint p = CheckPoints.Pop();

            var deletedAnimations = new List<Animation>();
            loadedAnimations.ForEach(anim =>
            {
                if (!anim.Permanent /*&& !p.animations.Contains(anim)*/)
                {
                    DeleteAnimation(anim);
                    deletedAnimations.Add(anim);
                }
            });
            deletedAnimations.ForEach(anim =>
                {
                    loadedAnimations.Remove(anim);
                });
           
            p.animations.ForEach(anim =>
                {
                    if (!loadedAnimations.Contains(anim))
                        RegisterAnimation(anim);
                }
                );
            luaVM.ProcessOneStep();

            //восстанавалием визуализационные объекты
            VDM.Scene.RollBack();
            ConnectsSceneObjects = p.ConnectsSceneObjects;

            //возвращаем сохраненные пины обратно
            foreach (var a in p.vpinsIn.Keys)
            {
                OnPinStateChanged(a, p.vpinsIn[a]);
            }
        }

        #endregion

        public void PinStateChanged(int virtualPin, int newState)
        {
            //OnPin(virtualPin, newState);
            Console.WriteLine("<ModellingSupervisor.PinStateChanged>: датчик {0}:{1}", virtualPin, newState);
            OnPinStateChanged(virtualPin, newState);
        }

        private Matrix4 MultiplyTransrorms(SceneNode s)
        {
            if (s.Name == "Root")
                return s.FullTransform;
            else
            {
                return s.FullTransform * MultiplyTransrorms((SceneNode)s.Parent);
            }
        }

        /// <summary>
        /// Вызывается при достижении узла, производящего сжатие/разжатие схвата, конечного состояния.
        /// </summary>
        /// <param name="animName">название анимации</param>
        /// <param name="openCloseID">открытие или закрытие</param>
        /// 
        public int OnAnimOpenClose(string animName, int openCloseID)
        {
            int ret = 0; //возвращение результата пересечения

            Console.WriteLine("<ModellingSupervisor.OnAnimOpenClose>: {0} {1}", animName, openCloseID == 0 ? "закрыла" : "открыла");

            //ТОЛЬКО РЕЖИМ ВИЗУАЛИЗАЦИИ ОБРАБАТЫВАЕМ!!!
            if (!isModelling)
                return 0;

            //найдем анимацию, которая нас вызвала
            Animation currentAnimation = allAnimations.Find(an => (an.Name == animName));

            if ((currentAnimation != null) && (currentAnimation.IsModelledAlways))
            {
                switch (openCloseID)
                {
                    //прицепление
                    case 0:
                        {
                            SceneObject openCloseNode = GetSceneObjectOfAnimationScript(VDM.Scene, currentAnimation.MethodID, currentAnimation.Name);

                            if (ConnectsSceneObjects.ContainsKey(openCloseNode.Name))
                            {
                                ConnectsSceneObjects[openCloseNode.Name].Animation = currentAnimation;
                                ret = 0;
                                break;
                            }

                            //*************************************
                            //след цикл для того, чтобы не схватывали себя за свои же конечности
                            //*************************************
                            SceneObject LastParent = openCloseNode;
                            while (LastParent.Parent != VDM.Scene)
                                LastParent = LastParent.Parent;

                            //Ищем первого того, с кем столкнулись - его будем привязывать к s
                            SceneObject InterSectObject = null;
                            int i = 0;
                            while (i < VDM.Scene.ChildSceneObjects.Count)
                            {
                                SceneObject o = VDM.Scene.ChildSceneObjects[i];
                                if (((o.TypeModelling & 0x000D) != 0) && ((o.TypeModelling & 0x0008) != 0))
                                    if ((o != LastParent) && (VisualizationService.AABBIntersectAABB(openCloseNode.SceneNode.WorldAABB, o.SceneNode.WorldAABB))) //а себя мы не будем хватать
                                    {
                                        InterSectObject = IntersectChildSceneObjects(openCloseNode, o);                                        
                                        if ((InterSectObject != null))// && (InterSectObject.TypeModelling != 2))//можно не брать всякие столики
                                        {
                                            break;
                                        }
                                    }
                                InterSectObject = null;
                                i++;
                            }
                            //---------------------------------

                            if (InterSectObject != null)
                            {
                                //проверяем наличие уже определённых пересечений,
                                //чтобы не было было бублирования
                                var cc = ConnectsSceneObjects.Keys.FirstOrDefault(meth => (ConnectsSceneObjects[meth].DependentOriginal == InterSectObject));
                                if (cc != null)
                                    break;

                                SceneObject Dependent = InterSectObject;

                                //штобы не потерять ссыль на предка, ибо нужно штобы предок обновился в аксиоме
                                SceneObject intersectParent = InterSectObject.Parent;

                                //если есть пересеченпие с нижней частью болванки
                                if ((InterSectObject.TypeModelling & 0x0020) != 0)
                                {
                                    if (ConnectsBolvanks.Keys.FirstOrDefault(meth => (ConnectsBolvanks[meth].SubBolv.Name == InterSectObject.Name)) != null)
                                    {
                                        //возможно произошло пересечение с обоими частями болванок. поэтому их нужно таскать вместе
                                        var temp = IntersectObjectsWithoutFirstObjectChilds(ConnectsBolvanks[InterSectObject.Name].MainBolv, openCloseNode);
                                        if (temp != null) // если надо таскать обе болванки
                                        {
                                            InterSectObject = ConnectsBolvanks[InterSectObject.Name].MainBolv;
                                            //штобы не потерять ссыль на предка, ибо нужно штобы предок обновился в аксиоме
                                            intersectParent = InterSectObject.Parent;
                                        }
                                        else //иначе расцепляем верхнюю и нижнюю части болванки
                                            ConnectsBolvanks.Remove(InterSectObject.Name);
                                    }
                                }
                                else //иначе схватили только верхнюю часть или что-то другое
                                {
                                    //чтобы не схватить себя
                                    while (InterSectObject.Parent != VDM.Scene)
                                        InterSectObject = InterSectObject.Parent;
                                }

                                #region преобразовываем позицию, ориентацию и размер

                                //SceneNode узла, который должны прицепить
                                var snToConnect = InterSectObject.SceneNode;

                                //VDM.Scene.SceneNode.FullTransform.Inverse() 
                                snToConnect.Position = openCloseNode.SceneNode.FullTransform.Inverse()
                                    * snToConnect.DerivedPosition;
                                float determ = openCloseNode.SceneNode.FullTransform.Determinant;
                                Quaternion q = new Quaternion();
                                q.FromRotationMatrix(openCloseNode.SceneNode.FullTransform.Inverse().GetMatrix3());
                                snToConnect.Orientation = q * snToConnect.DerivedOrientation;

                                snToConnect.Scale(InterSectObject.Parent.SceneNode.DerivedScale);
                                snToConnect.Scale(Axiom.Math.Vector3.UnitScale / openCloseNode.SceneNode.DerivedScale);
                                
                                //старая версия                                
                                //InterSectObject.SceneNode.Position = openCloseNode.SceneNode.FullTransform.Inverse() *
                                //        InterSectObject.SceneNode.DerivedPosition;

                                //Quaternion q = new Quaternion();
                                //q.FromRotationMatrix(openCloseNode.SceneNode.FullTransform.Inverse().GetMatrix3());
                                //InterSectObject.SceneNode.Orientation = q * InterSectObject.SceneNode.DerivedOrientation;

                                //InterSectObject.SceneNode.Scale(InterSectObject.Parent.SceneNode.DerivedScale);
                                //InterSectObject.SceneNode.Scale(Axiom.Math.Vector3.UnitScale / openCloseNode.SceneNode.DerivedScale);

                                #endregion


                                #region меняем привязки и учитываем соединение (Link)

                                InterSectObject.Parent.ChildSceneObjects.Remove(InterSectObject);
                                openCloseNode.AddChildSceneObject(InterSectObject);
                                ConnectsSceneObjects[openCloseNode.Name] = new SceneObjectsLink(openCloseNode, InterSectObject, Dependent, InterSectObject.Parent, currentAnimation);

                                #endregion

                                //привязываем к узлу сцены - в самом конце, пока sn.Parent не поменялся
                                openCloseNode.SceneNode.AddChild(InterSectObject.SceneNode);
                                //обновить часть сцены
                                openCloseNode.SceneNode.NeedUpdate();
                                InterSectObject.SceneNode.NeedUpdate();
                                intersectParent.SceneNode.NeedUpdate();

                                ret = 1;
                            }
                            else ret = 0;
                            break;
                        }
                    case 1:
                        {
                            SceneObject s = GetSceneObjectOfAnimationScript(VDM.Scene, currentAnimation.MethodID, currentAnimation.Name);
                            if (ConnectsSceneObjects.ContainsKey(s.Name))
                            {
                                //--Связываем объекты
                                Axiom.Math.Vector3 v = ConnectsSceneObjects[s.Name].Dependent.SceneNode.DerivedPosition;
                                Axiom.Math.Quaternion or = ConnectsSceneObjects[s.Name].Dependent.SceneNode.DerivedOrientation;
                                Axiom.Core.SceneNode sn = ConnectsSceneObjects[s.Name].Dependent.SceneNode;//пузель, который отцепляем

                                Axiom.Math.Matrix4 m = VDM.Scene.SceneNode.FullTransform;

                                #region преобразовываем позицию, ориентацию и размер

                                sn.Position = VDM.Scene.SceneNode.FullTransform.Inverse() * sn.DerivedPosition;

                                Quaternion q = new Quaternion();
                                q.FromRotationMatrix(VDM.Scene.SceneNode.FullTransform.Inverse().GetMatrix3());
                                sn.Orientation = q * sn.DerivedOrientation;

                                sn.Scale(ConnectsSceneObjects[s.Name].Dependent.Parent.SceneNode.DerivedScale);
                                sn.Scale(Axiom.Math.Vector3.UnitScale / VDM.Scene.SceneNode.DerivedScale);

                                #endregion

                                VDM.Scene.AddChildSceneObject(ConnectsSceneObjects[s.Name].Dependent);
                                VDM.Scene.SceneNode.AddChild(sn);
                                s.ChildSceneObjects.Remove(ConnectsSceneObjects[s.Name].Dependent);
                                ConnectsSceneObjects.Remove(s.Name);
                                s.SceneNode.NeedUpdate();
                                VDM.Scene.SceneNode.NeedUpdate();
                            }

                            ret = 0;
                            break;
                        }
                }
            }
            return ret;
        }

        #region Animations

        /// <summary>
        /// Список всех анимаций
        /// </summary>
        List<Animation> allAnimations = new List<Animation>();

        /// <summary>
        /// Список работающих анимаций
        /// </summary>
        List<Animation> loadedAnimations = new List<Animation>();

        private void Script_Error(object actor, object[] messages)
        {
            var activeobj = actor as ActiveObject;

            var mess = messages.Aggregate(activeobj.Name + "\n" + activeobj.LuaProgram + "\nDoString returned: ",
                (acc, obj) => acc += "\n" + obj.ToString());

            mess = mess.Replace(">", " &gt; ");
            mess = mess.Replace("<", " &lt; ");

            logger.WriteMessage(MessagePriority.Error,
                    "Modelling: Runtime error in script: " + mess);
        }

        private void Script_OnDone(object sender, EventArgs e)
        {
            var activeObj = sender as ActiveObject;

            Animation anim = loadedAnimations.Find(a => a.Name == activeObj.Name);
            if (anim != null)
                loadedAnimations.Remove(anim);

            KillSubscribeOnActor(activeObj);
        }

        /// <summary>
        /// Подписаться на некоторые события анимаций
        /// </summary>
        /// <param name="actor"></param>
        private void SubscribeOnActor(ActiveObject actor)
        {
            actor.OnDone += new EventHandler(Script_OnDone);
            actor.OnRunTimeErrorOccured += new RunTimeErrorHandler(Script_Error);
        }

        /// <summary>
        /// Снят подписку на событие анимаций
        /// </summary>
        /// <param name="actor"></param>
        private void KillSubscribeOnActor(ActiveObject actor)
        {
            actor.OnDone -= new EventHandler(Script_OnDone);
            actor.OnRunTimeErrorOccured -= new RunTimeErrorHandler(Script_Error);
        }

        private void RegisterAnimation(Animation animation)
        {
            var actor = new ActiveObject(animation.Name);
            actor.LuaProgram = animation.LuaCode;
            actor.ScriptOneStepCode = "coroutine.resume(" + animation.Name + ")";
            SubscribeOnActor(actor);
            luaVM.RegisterActiveObject(actor);

            loadedAnimations.Add(animation);
        }

        public void UnregisterAnimation(Animation animation)
        {
            if (loadedAnimations.Contains(animation))
            {
                if (animation.LuaCodeOnDelete != null)
                {
                    var actor = new ActiveObject(animation.Name + "Delete");
                    actor.LuaProgram = animation.LuaCodeOnDelete;
                    actor.ScriptOneStepCode = "coroutine.resume(" + actor.Name + ")";
                    SubscribeOnActor(actor);
                    luaVM.RegisterActiveObject(actor);
                }
                luaVM.UnRegisterActiveObjectByName(animation.Name);
                loadedAnimations.Remove(animation);
            }
        }

        public void DeleteAnimation(Animation animation)
        {
            luaVM.UnRegisterActiveObjectByName(animation.Name);
        }

        #endregion

        /// <summary>
        /// Указать, какой режим используется - моделирование или исполнение на реальной уставноке
        /// Если моделирование, то Modelling моделирует работу всех устройств на визуализаторе.
        /// Если исполнение - то только вызовы IsModellingAlways-методов.
        /// </summary>
        public void SetMode(bool isModelling)
        {
            this.isModelling = isModelling;
        }
    }
}
